package com.nkym.wypan.service.impl;

import javax.servlet.http.HttpServletResponse;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.nkym.wypan.constants.CommonConstants;
import com.nkym.wypan.constants.RedisKey;
import com.nkym.wypan.enums.FileDelFlag;
import com.nkym.wypan.enums.FolderFlag;
import com.nkym.wypan.enums.ResponseStatus;
import com.nkym.wypan.enums.share.ShareDayType;
import com.nkym.wypan.enums.share.ShareStatus;
import com.nkym.wypan.exception.BusinessException;
import com.nkym.wypan.mapper.ShareMapper;
import com.nkym.wypan.model.context.share.*;
import com.nkym.wypan.model.entity.*;
import com.nkym.wypan.model.req.share.CheckCodeReq;
import com.nkym.wypan.model.resp.share.*;

import com.nkym.wypan.model.resp.user.UserShareInfoVO;
import com.nkym.wypan.model.resp.userfile.BreadcrumbVo;
import com.nkym.wypan.model.resp.userfile.QueryUserFileVo;
import com.nkym.wypan.service.*;
import com.nkym.wypan.storage.core.StorageEngine;
import com.nkym.wypan.utils.IdUtils;
import com.nkym.wypan.utils.JwtUtils;
import com.nkym.wypan.utils.UserContextHolder;
import org.assertj.core.util.Lists;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * @author 19637
 * @description 针对表【wy_pan_share(用户分享表)】的数据库操作Service实现
 * @createDate 2023-08-16 14:00:16
 */
@Service
public class ShareServiceImpl extends ServiceImpl<ShareMapper, Share>
        implements ShareService {
    @Resource
    private ShareMapper shareMapper;
    @Resource
    UserInfoService userInfoService;
    @Resource
    StorageEngine storageEngine;
    @Resource
    FileInfoService fileInfoService;
    @Resource
    UserFileService userFileService;
    @Resource
    private ShareFileService shareFileService;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Value("${share.prefix}")
    private String urlPrefix;


    @Override
    public void download(ShareDownloadContext shareDownloadContext) {
        String code = shareDownloadContext.getCode();
        HttpServletResponse response = shareDownloadContext.getResponse();
        fileInfoService.download(code, response);
    }

    /**
     * 查询当前所有目录文件
     *
     * @param context
     * @return
     */
    @Override
    public List<FolderVO> listFolder(ListFolderContext context) {
        Long parentId = context.getParentId();
        Long userId = context.getUserId();


        List<UserFile> userFileList = userFileService.list(new LambdaQueryWrapper<UserFile>()
                .eq(UserFile::getUserId, userId)
                .eq(UserFile::getParentId, parentId)
        );
        List<FolderVO> res = userFileList.stream()
                .filter(r -> FolderFlag.YES.getCode().equals(r.getFolderFlag()))
                .map(FolderVO::covert)
                .collect(Collectors.toList());
        return res;
    }

    /**
     * 校验分享状态
     * 校验 文件状态
     *
     * @param breadcrumbContext
     * @return
     */
    @Override
    public List<BreadcrumbVo> breadcrumb(BreadcrumbContext breadcrumbContext) {
        Long fileId = breadcrumbContext.getFileId();
        Long shareId = breadcrumbContext.getShareId();
        checkShareStatus(shareId);
        List<UserFile> userFileList = checkFileExistShareFile(shareId, Lists.newArrayList(fileId));
        if (userFileList.isEmpty()) {
            return Lists.newArrayList();
        }
        // ID 和当前ID 实体对象的映射
        Map<Long, UserFile> userFileMap = userFileList.stream().collect(Collectors.toMap(UserFile::getFileId, r -> r));
        UserFile userFile = userFileMap.get(fileId);
        if (Objects.equals(userFile.getFolderFlag(), FolderFlag.NO.getCode())) {
            throw new BusinessException(ResponseStatus.FILE_IS_NOT_FOLDER);
        }
        List<UserFile> res = Lists.newArrayList();
        do {
            UserFile parentFile = userFileMap.get(fileId);
            if (parentFile == null) break;
            res.add(0, parentFile);
            fileId = parentFile.getParentId();
        } while (true);

        return res.stream().map(BreadcrumbVo::transfer).collect(Collectors.toList());
    }

    @Override
    public String getDownloadCode(GetCodeContext codeContext) {
        Long shareId = codeContext.getShareId();
        Long fileId = codeContext.getFileId();
        checkShareStatus(shareId);
        UserFile userFile = userFileService.getById(fileId);
        if (userFile == null) {
            throw new BusinessException(ResponseStatus.INVALID_SOURCE);
        }
        if (Objects.equals(userFile.getFolderFlag(), FolderFlag.YES.getCode())) {
            throw new BusinessException("目录不提供下载");
        }
        checkFileExistShareFile(shareId, Lists.newArrayList(fileId));
        //生成下载码
        String sourceCode = RandomUtil.randomString(50);
        redisTemplate.opsForValue().set(RedisKey.SOURCE_CODE_KEY + sourceCode, userFile, CommonConstants.THREE, TimeUnit.HOURS);
        return sourceCode;
    }

    /**
     * 1.保存文件分享记录
     * 2.保存 文件分享记录和文件的映射关系
     * 3.返回分享结果。
     */
    @Override
    @Transactional
    public CreateShareVO createShare(CreateShareContext context) {
        saveShare(context);
        saveShareFile(context);
        return assembleCreateShareVO(context);
    }

    /**
     * 1. 自己分享的文件不能保存
     * 2. 校验分享状态
     * 3. 校验文件列表是否在 分享列表中
     * 4. 转存文件到用户的文件中
     * 5. 计算并且扣除用户的可用空间。
     *
     * @param context
     */
    @Transactional
    @Override
    public void saveShare(SaveShareContext context) {
        Long shareId = context.getShareId();
        Long userId = context.getUserId();
        List<Long> fileIdList = context.getFileIdList();


        //校验分享状态
        Share share = checkShareStatus(shareId);


        //自己分享的文件不能保存
        if (Objects.equals(share.getCreateUser(), userId)) {
            throw new BusinessException("不能保存自己的分享");
        }


        // 校验需要保存的文件列表是否在分享列表中
        List<UserFile> allShareFileList = checkFileExistShareFile(shareId, fileIdList);

        //ID---->Object  ID和对象的映射
        Map<Long, List<UserFile>> parentIdForObjectMap = allShareFileList.stream().collect(Collectors.groupingBy(UserFile::getParentId));

        //转存文件到用户的文件中 计算并且扣除用户的可用空间
        doSaveShare(context, parentIdForObjectMap);
    }


    @Override
    public List<ShareVO> shares() {
        Long userId = UserContextHolder.getId();
        List<ShareVO> res = shareMapper.shares(userId);
        return res;
    }

    /**
     * 校验权限 是否是自己的分享
     * 取消分享
     * 取消分享记录和 文件的映射关系
     *
     * @param context
     */
    @Transactional
    @Override
    public void cancel(CancelShareContext context) {
        checkCancelPermission(context);
        doCancelShare(context);
        doCancelShareFile(context);
    }

    /**
     * 1.校验分享状态
     * 2.校验分享验证码
     * 3.返回结果
     *
     * @param req
     * @return
     */
    @Override
    public CheckCodeVO checkCode(CheckCodeReq req) {
        Long shareId = req.getShareId();
        String shareCode = req.getShareCode();
        Share record = checkShareStatus(shareId);
        verifyShareCode(shareCode, record);
        String shareToken = JwtUtils.createShareToken(record.getShareId());

        CheckCodeVO vo = new CheckCodeVO();
        vo.setShareToken(shareToken);
        return vo;
    }

    /**
     * 1.校验分享 ID 是否有效
     * 2. 查询用户信息
     * 3. 查询文件信息
     *
     * @param context
     * @return
     */
    @Override
    public DetailShareVO shareDetail(DetailShareContext context) {
        Long shareId = context.getShareId();
        Share share = checkShareStatus(shareId);
        Long userId = share.getCreateUser();
        UserShareInfoVO userShareInfoVO = getUserInfo(userId);
        List<QueryUserFileVo> fileInfoList = getFileInfoList(shareId);

        DetailShareVO detailShareVO = assembleDetailShareVO(share, userShareInfoVO, fileInfoList);
        return detailShareVO;
    }

    @NotNull
    private static DetailShareVO assembleDetailShareVO(Share share, UserShareInfoVO userShareInfoVO, List<QueryUserFileVo> fileInfoList) {
        DetailShareVO detailShareVO = new DetailShareVO();
        detailShareVO.setShareName(share.getShareName());
        detailShareVO.setCreateTime(share.getCreateTime());
        detailShareVO.setShareEndTime(share.getShareEndTime());
        detailShareVO.setShareType(share.getShareType());
        detailShareVO.setShareDayType(share.getShareDayType());
        detailShareVO.setUserInfo(userShareInfoVO);
        detailShareVO.setFileInfoList(fileInfoList);
        return detailShareVO;
    }

    @Override
    public SimpleShareVO simpleShare(DetailShareContext context) {
        Long shareId = context.getShareId();
        Share share = checkShareStatus(shareId);
        Long userId = share.getCreateUser();
        UserShareInfoVO userShareInfoVO = getUserInfo(userId);
        SimpleShareVO vo = new SimpleShareVO();
        vo.setUserInfo(userShareInfoVO);
        return vo;
    }

    /**
     * 1.校验分享状态
     * 2.校验文件是否在分享文件中 返回所有分享中的文件
     * 3.返回目录所有文件
     *
     * @param context
     * @return
     */
    @Override
    public List<QueryUserFileVo> nextFolder(NextFolderContext context) {
        Share record = checkShareStatus(context.getShareId());
        List<UserFile> res = checkFileExistShareFile(record.getShareId(), Lists.newArrayList(context.getParentId()));
        //父文件ID  和 子文件的映射
        Map<Long, List<UserFile>> parentIdMap = res.stream().collect(Collectors.groupingBy(UserFile::getParentId));
        List<UserFile> userFileList = parentIdMap.get(context.getParentId());
        if (userFileList == null) {
            return Lists.newArrayList();
        }
        List<QueryUserFileVo> r = userFileList.stream().map(QueryUserFileVo::covert).collect(Collectors.toList());
        return r;
    }


    /**
     * ---------------------------------------private---------------------------------------------------------------------------------------------------------
     */
    /**
     * 校验当前所有需要查询的文件是否存在于分享文件列表中
     * 1.递归查询所有的分享文件
     *
     * @param
     * @param
     * @return
     */
    private List<UserFile> checkFileExistShareFile(Long shareId, List<Long> fileIdList) {
        List<Long> shareFileIdList = getFileIdListByShareId(shareId);

        List<UserFile> allFileRecords = userFileService.findAllFile(shareFileIdList);

        allFileRecords = allFileRecords.stream()
                .filter(Objects::nonNull)
                .filter(r -> r.getDelFlag() == FileDelFlag.NORMAL.getCode())
                .collect(Collectors.toList());
        List<Long> allShareFileIdList = allFileRecords.stream().map(UserFile::getFileId).collect(Collectors.toList());
        if (allShareFileIdList.containsAll(fileIdList)) {
            return allFileRecords;
        }
        throw new BusinessException(ResponseStatus.ILLEGAL_SHARE_FILE);
    }

    private List<QueryUserFileVo> getFileInfoList(Long shareId) {
        List<Long> fileIdList = getFileIdListByShareId(shareId);
        if (fileIdList.isEmpty()) {
            throw new BusinessException(ResponseStatus.CANCEL_SHARE_FAIL);
        }
        List<UserFile> fileList = userFileService.list(new LambdaQueryWrapper<UserFile>()
                .eq(UserFile::getDelFlag, FileDelFlag.NORMAL)
                .in(UserFile::getFileId, fileIdList));
        if (fileList.isEmpty()) {
            throw new BusinessException(ResponseStatus.CANCEL_SHARE_FAIL);
        }
        List<QueryUserFileVo> res = fileList.stream()
                .map(QueryUserFileVo::covert)
                .collect(Collectors.toList());
        return res;
    }

    @NotNull
    private List<Long> getFileIdListByShareId(Long shareId) {
        List<Long> fileIdList = shareFileService.listObjs(new LambdaQueryWrapper<ShareFile>()
                        .select(ShareFile::getFileId)
                        .eq(ShareFile::getShareId, shareId)
                ).stream()
                .map(r -> (Long) r)
                .collect(Collectors.toList());
        return fileIdList;
    }

    private UserShareInfoVO getUserInfo(Long userId) {
        UserInfo userInfo = userInfoService.getById(userId);
        if (userInfo == null) {
            throw new BusinessException(ResponseStatus.USER_NOT_EXIST);
        }
        UserShareInfoVO userShareInfoVO = new UserShareInfoVO();
        userShareInfoVO.setUserId(userInfo.getUserId());
        userShareInfoVO.setNickname(userInfo.getNickname());
        userShareInfoVO.setAvatar(userInfo.getAvatar());
        return userShareInfoVO;
    }

    private void verifyShareCode(String shareCode, Share record) {
        String realCode = record.getShareCode();
        if (!shareCode.equals(realCode)) {
            throw new BusinessException(ResponseStatus.CODE_ERROR);
        }

    }

    private Share checkShareStatus(Long shareId) {
        Share share = shareMapper.selectById(shareId);
        if (share == null || !Objects.equals(share.getShareStatus(), ShareStatus.NORMAL.getCode())) {
            throw new BusinessException(ResponseStatus.SHARE_CANCEL);
        }
        if (!share.getShareDayType().equals(ShareDayType.PERMANENT.getCode()) && DateUtil.compare(new Date(), share.getShareEndTime()) > 0) {
            throw new BusinessException(ResponseStatus.SHARE_CANCEL);
        }
        return share;
    }

    private void checkCancelPermission(CancelShareContext context) {
        List<Long> shareIdList = context.getShareIdList();
        Long userId = context.getUserId();
        List<Share> shares = shareMapper.selectBatchIds(shareIdList);
        if (shares.isEmpty()) {
            throw new BusinessException(ResponseStatus.CANCEL_SHARE_FAIL);
        }
        boolean b = shares.stream().
                allMatch(a -> Objects.equals(a.getCreateUser(), userId));
        if (!b) {
            throw new BusinessException(ResponseStatus.CANCEL_SHARE_FAIL);
        }
    }

    private void doCancelShare(CancelShareContext context) {
        List<Long> shareIdList = context.getShareIdList();

        if (!removeBatchByIds(shareIdList)) {
            throw new BusinessException(ResponseStatus.CANCEL_SHARE_FAIL);
        }


    }

    private void doCancelShareFile(CancelShareContext context) {
        List<Long> shareIdList = context.getShareIdList();
        Long userId = context.getUserId();
        boolean b = shareFileService.remove(new LambdaQueryWrapper<ShareFile>()
                .eq(ShareFile::getCreateUser, userId)
                .in(ShareFile::getShareId, shareIdList));
        if (!b) {
            throw new BusinessException(ResponseStatus.CANCEL_SHARE_FAIL);
        }

    }


    private CreateShareVO assembleCreateShareVO(CreateShareContext context) {
        Share share = context.getShare();
        CreateShareVO shareVO = new CreateShareVO();
        shareVO.setShareUrl(share.getShareUrl());
        shareVO.setShareCode(share.getShareCode());
        shareVO.setShareType(share.getShareType());
        shareVO.setShareName(share.getShareName());
        return shareVO;
    }

    private void saveShareFile(CreateShareContext context) {
        List<Long> fileIdList = context.getFileIdList();
        Long userId = context.getUserId();
        Share share = context.getShare();
        Long shareId = share.getShareId();
        List<ShareFile> shareFileList = Lists.newArrayList();
        for (Long fileId : fileIdList) {
            ShareFile shareFile = new ShareFile();
            shareFile.setFileId(fileId);
            shareFile.setShareId(shareId);
            shareFile.setCreateUser(userId);
            shareFileList.add(shareFile);
        }
        if (!shareFileService.saveBatch(shareFileList)) {
            throw new BusinessException(ResponseStatus.CREATE_SHARE_FAIL);
        }
    }

    private void saveShare(CreateShareContext context) {
        Share share = generateShareEntity(context);
        int insert = shareMapper.insert(share);
        if (insert < 1) {
            throw new BusinessException(ResponseStatus.CREATE_SHARE_FAIL);
        }
        Share realShare = shareMapper.selectById(share.getShareId());
        if (realShare == null) {
            throw new BusinessException(ResponseStatus.CREATE_SHARE_FAIL);
        }
        context.setShare(realShare);
    }

    private Share generateShareEntity(CreateShareContext context) {
        String shareName = context.getShareName();
        Integer shareType = context.getShareType();
        Integer shareDayType = context.getShareDayType();
        String shareCode = context.getShareCode();
        Long userId = context.getUserId();
        Share share = new Share();
        long shareId = IdUtils.getId();
        share.setShareId(shareId);
        share.setShareName(shareName);
        share.setShareType(shareType);
        share.setShareDayType(shareDayType);
        Integer validTime = ShareDayType.getDaysForDayType(shareDayType);
        if (validTime == -1) {
            throw new BusinessException(ResponseStatus.INVALID_SHARE_TIME);
        }
        share.setShareDay(validTime);
        share.setShareEndTime(DateUtil.offsetDay(new Date(), validTime));
        share.setShareUrl(generateUrl(shareId));
        //    需要提取码 才会生成提取码
        if ((shareCode == null || "".equals(shareCode))) {
            shareCode = RandomUtil.randomString(4);
        }
        share.setShareCode(shareCode);
        share.setShareStatus(ShareStatus.NORMAL.getCode());
        share.setCreateUser(userId);
        return share;
    }

    private String generateUrl(Long shareId) {
        if (urlPrefix == null || urlPrefix.length() < 1) {
            urlPrefix = getDefaultUrlPreFix();
        }
        int length = urlPrefix.length();
        if (urlPrefix.charAt(length - 1) != '/') {
            urlPrefix = urlPrefix + "/";
        }
        return urlPrefix + shareId;
    }

    private void doSaveShare(SaveShareContext context, Map<Long, List<UserFile>> parentIdForObjectMap) {
        List<Long> fileIdList = context.getFileIdList();
        Long folderId = context.getFolderId();
        //查询当前需要保存文件
        List<UserFile> userFileList = userFileService.listByIds(fileIdList);

        UserInfo userInfo = userInfoService.getById(context.getUserId());
        //计算用户剩余空间
        Long freeSpace = userInfo.getTotalSpace() - userInfo.getUseSpace();
        List<UserFile> allNeedDumpUserFile = Lists.newArrayList();
        //转储用户文件 递归保存子目录
        Long useSpace = doDumpUserFileAndReduceUserSpace(folderId, allNeedDumpUserFile, context.getUserId(), parentIdForObjectMap, userFileList, freeSpace);
        userFileService.saveBatch(allNeedDumpUserFile);
        userInfoService.addUsedSpace(useSpace);
    }

    private Long doDumpUserFileAndReduceUserSpace(Long parentId, List<UserFile> allNeedDumpUserFile, Long userId,
                                                  Map<Long, List<UserFile>> parentIdForObjectMap, List<UserFile> userFileList,
                                                  Long freeSpace) {
        Long useSpace = 0L;
        if (userFileList == null || userFileList.isEmpty()) {
            return useSpace;
        }
        for (UserFile uf : userFileList) {
            if (Objects.equals(uf.getFolderFlag(), FolderFlag.YES.getCode())) {
                List<UserFile> childUFList = parentIdForObjectMap.get(uf.getFileId());
                long newFileId = IdUtils.getId();
                uf.setFileId(newFileId);
                uf.setParentId(parentId);
                uf.setUserId(userId);
                uf.setCreateUser(userId);
                uf.setUpdateUser(userId);
                allNeedDumpUserFile.add(uf);
                useSpace += doDumpUserFileAndReduceUserSpace(newFileId, allNeedDumpUserFile, userId, parentIdForObjectMap, childUFList, freeSpace);
                continue;
            }
            if (freeSpace - uf.getFileSize() < 0) {
                break;
            }
            long newFileId = IdUtils.getId();
            uf.setFileId(newFileId);
            uf.setParentId(parentId);
            uf.setUserId(userId);
            uf.setUpdateUser(userId);
            uf.setCreateUser(userId);
            freeSpace -= uf.getFileSize();
            useSpace += uf.getFileSize();
            allNeedDumpUserFile.add(uf);
        }
        return useSpace;
    }

    private String getDefaultUrlPreFix() {
        return "http://localhost:8088/api/share";
    }


}




